CheckHoverContrast Test Step Documentation
Overview
The CheckHoverContrast test step verifies that interactive elements comply with WCAG 2.1 SC 1.4.11 (Non-text Contrast) requirements for hover state changes. Specifically, it checks that when a color change is the only visual cue indicating the hover state, the contrast ratio between the default and hover states is at least 3:1.
Purpose
Interactive controls often change appearance on hover to indicate interactivity. When this change relies solely on color (without other visual cues like cursor changes, shadows, or borders), users with low vision may not perceive the state change. This test ensures sufficient contrast between states for accessibility.
WCAG Reference
- Success Criterion: 1.4.11 Non-text Contrast (Level AA)
- Requirement: Visual information required to identify UI components and states must have a contrast ratio of at least 3:1 against adjacent colors
- Applies to: Hover state changes where color is the only visual indicator
References
How It Works
1. Element Selection
The test targets elements from visually_graphic_elements (icons, buttons with graphics) since these are most likely to have color-only hover states without cursor changes.
2. Pre-Check Filters (Skip Conditions)
Before testing, elements are skipped if they:
- Are not visible
- Are disabled (
disabledattribute oraria-disabled="true")
3. Visual Cue Detection
The test only applies when color change is the sole visual indicator. Elements are skipped if they have other visual cues:
| Visual Cue | Detection Method |
|---|---|
| Interactive cursor | pointer, hand, grab, etc. in default or hover state |
| Cursor change | Any cursor change to a meaningful value |
| Shadow appearing | box-shadow or text-shadow added on hover |
| Border changes | Border width, style, or visibility changes |
| Transform changes | transform property changes (scale, rotate, etc.) |
| Size changes | Element dimensions change by >2px |
| SVG transforms | Transform on child SVG elements |
| Outline changes | Outline width or style changes |
| Text decoration | Underline/strikethrough changes |
| Opacity changes | Opacity difference >0.1 |
| Browser defaults | Default browser hover styling (e.g., link underlines) |
4. Color Extraction Priority
For graphic elements (icons), colors are extracted in this priority order:
- SVG child colors -
fillandstrokefrom<svg>,<path>,<circle>, etc. - Graphic properties -
fill,stroke,coloron the element itself - Container properties -
background-color, border colors
This ensures we measure the control's color change, not the background.
5. Screenshot-Based Comparison
The test uses a robust screenshot comparison approach:
- Scroll element into view
- Capture default state screenshot
- Hover the element (wait 300ms for styles to apply)
- Capture hover state screenshot
- Compare screenshots to detect visual change
- Calculate contrast ratio using image analysis with CSS color hints
6. Contrast Calculation
The contrast ratio is calculated between:
- Default state color - the element's color before hover
- Hover state color - the element's color after hover
The comparison uses get_hover_contrast_ratio() which:
- Analyzes pixel differences between screenshots
- Uses CSS color hints when available
- Falls back to dominant color analysis when hints are unavailable
Report Format
When a violation is found:
| Field | Value |
|---|---|
| Title | "Insufficient contrast against the control and background" |
| Description | The <color name> (#hex color) of "<control name>" control on hover fails color contrast against the <color name> background (#hex color) with a ratio of <contrast ratio>. |
| Failed WCAG CP | 1.4.11 |
Configuration
CheckHoverContrast(
debug=False, # Enable debug logging
target_key="visually_graphic_elements" # Key in context.data for elements to check
)Constants
WCAG_NON_TEXT_CONTRAST_THRESHOLD: 3.0 (minimum required contrast ratio)INTERACTIVE_CURSOR_VALUES: Set of cursor values that indicate visual cues (pointer,hand,grab, etc.)
Limitations
1. Background Images
- Issue: If an element uses a
background-imageinstead ofbackground-color, CSS-based color extraction cannot capture the color. - Mitigation: The screenshot-based comparison still works by analyzing rendered pixels, but accuracy may be reduced.
- Edge case: If hover changes the background image itself (not just overlays a color), contrast calculation may not be accurate.
2. Complex Gradients
- Issue: Elements with gradient backgrounds (
linear-gradient,radial-gradient) don't have a single color value. - Mitigation: Screenshot analysis attempts to find dominant colors, but results may vary.
3. Pseudo-elements
- Issue: Hover effects applied via
::beforeor::afterpseudo-elements may not be captured byget_all_styles(). - Mitigation: Screenshot comparison catches visual changes, but CSS color hints may be missing.
4. Animations/Transitions
- Issue: Elements with CSS transitions may not have fully transitioned when the hover screenshot is taken.
- Mitigation: 300ms delay after hover, but very slow transitions may not complete.
5. JavaScript-Driven Hover Effects
- Issue: Hover effects triggered by JavaScript
mouseenterevents may have timing issues. - Mitigation: The 300ms delay helps, but complex JS interactions may not be fully captured.
6. Nested Interactive Elements
- Issue: Nested buttons/links may have hover effects that interfere with each other.
- Current behavior: Each element is tested individually.
7. Dynamic Content
- Issue: Elements that load content dynamically on hover (tooltips, dropdowns) may affect screenshots.
- Mitigation: These typically have other visual cues and would be skipped.
8. SVG Sprite Icons
- Issue: Icons using SVG sprites (
<use xlink:href>) may have colors defined in the sprite, not on the element. - Mitigation:
_get_svg_child_colors()attempts to extract colors from child elements.
9. Icon Fonts
- Issue: Icon fonts (FontAwesome, etc.) use the
colorproperty, which is checked, but may be inherited from parent elements. - Mitigation: CSS
colorproperty is included in graphic color properties.
10. High-Contrast Mode
- Issue: Users with Windows High Contrast Mode may see different colors than what's tested.
- Current behavior: Test runs with standard browser rendering.
Out of Scope
The following are explicitly not tested by this step:
- Disabled components - Skipped automatically
- Elements with other visual cues - Cursor changes, shadows, borders, etc.
- Text contrast - Covered by WCAG 1.4.3, not 1.4.11
- Focus state contrast - Handled by
CheckFocusIndicatorContrast - Active/visited states - Only hover state is tested
- Browser default hover colors - Skipped as browser responsibility
Dependencies
Internal Modules
BaseTestStep,TestStepResult- Base test infrastructureElement- Page element wrapper with hover/screenshot capabilitiesBaseLogger- Logging infrastructure
Utility Functions
src.utils.color- Color parsing and conversion utilitiessrc.vision.comparison- Image comparison and contrast calculationcompare_byte_images()- Detect visual differencesget_hover_contrast_ratio()- Calculate contrast between states
Example Usage
from src.a11y_test_definitions.test_step.visually_disabled.CheckHoverContrast import CheckHoverContrast
# Create test step
hover_contrast_check = CheckHoverContrast(debug=True)
# Run as part of a test case
result = hover_contrast_check.run(context, logger)Debug Mode
When debug=True, the test outputs detailed step-by-step information:
[CheckHoverContrast] Step 1: Checking visibility...
[CheckHoverContrast] Step 2: Checking disabled state...
[CheckHoverContrast] Step 3: Getting default cursor/styles...
[CheckHoverContrast] -> Default cursor: auto
[CheckHoverContrast] -> Default SVG colors: {'fill': 'rgb(102, 102, 102)', 'stroke': ''}
[CheckHoverContrast] Step 4: Scrolling into view and taking default screenshot...
[CheckHoverContrast] Step 5: Hovering element...
[CheckHoverContrast] Step 6: Getting hover cursor...
[CheckHoverContrast] -> Hover cursor: auto
[CheckHoverContrast] Step 7: Getting hover styles...
[CheckHoverContrast] -> Hover SVG colors: {'fill': 'rgb(0, 102, 204)', 'stroke': ''}
[CheckHoverContrast] Step 10: Comparing screenshots...
[CheckHoverContrast] Step 11: Extracting color hints...
[CheckHoverContrast] -> CSS colors: default=(102, 102, 102), hover=(0, 102, 204)
[CheckHoverContrast] Step 12: Calculating contrast ratio...
[CheckHoverContrast] -> Contrast: 2.45:1 (fg=(0, 102, 204), bg=(102, 102, 102))Version History
| Version | Changes |
|---|---|
| 1.0 | Initial implementation with CSS-based color extraction |
| 1.1 | Added SVG child color extraction for icon support |
| 1.2 | Added screenshot-based comparison for robustness |
| 1.3 | Added comprehensive visual cue detection (cursor, shadow, transform, etc.) |
